package com.george.shiro.config;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.george.shiro.enums.UserStatusEnum;
import com.george.shiro.enums.UserTypeEnum;
import com.george.shiro.model.entity.Resources;
import com.george.shiro.model.entity.Role;
import com.george.shiro.model.entity.User;
import com.george.shiro.model.service.IResourcesService;
import com.george.shiro.model.service.IRoleService;
import com.george.shiro.model.service.IUserService;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * <p>
 *
 * </p>
 *
 * @author GeorgeChan 2019/5/21 22:42
 * @version 1.0
 * @since jdk1.8
 */
public class ShiroRealm extends AuthorizingRealm {
    private static final Logger LOGGER = LoggerFactory.getLogger(ShiroRealm.class);

    @Resource
    private IUserService userService;
    @Resource
    private IRoleService roleService;
    @Resource
    private IResourcesService resourcesService;

    /**
     * 认证
     *AuthenticationInfo对象中存储的是主体（Subject）的身份认证信息。
     * Shiro会调用CredentialsMatcher对象的doCredentialsMatch方法
     * 对AuthenticationInfo对象和AuthenticationToken进行匹配。
     * 匹配成功则表示主体（Subject）认证成功，否则表示认证失败。
     * @param token token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        LOGGER.info("开始执行认证操作");
        // 获取登录用户名
        String username = (String) token.getPrincipal();
        User user = userService.findByUsername(username);
        if (ObjectUtil.isNull(user)) {
            throw new UnknownAccountException("账号不存在！");
        }
        if (ObjectUtil.isNotNull(user.getStatus())
            && UserStatusEnum.DISABLE.getCode().equals(user.getStatus())) {
            throw new LockedAccountException("账号已被锁定！");
        }
        // principal参数使用用户Id，方便动态刷新用户权限
        return new SimpleAuthenticationInfo(user.getId(), user.getPassword(), ByteSource.Util.bytes(user.getUsername()), getName());
    }

    /**
     * 授权，为当前登录的Subject授予角色和权限（角色的权限信息集合）
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        LOGGER.info("开始执行授权操作");
        // 权限对象info，用来存放当前用户所有的角色（role）和权限（permission）
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        Subject subject = SecurityUtils.getSubject();
        Long userId = (Long) subject.getPrincipal();
//        LOGGER.info("subject对象的值为 ===》 {}", JSON.toJSONString(subject));
//        LOGGER.info("subject.getPrincipal()对象的值为 ===》 {}", JSON.toJSONString(subject.getPrincipal()));

        // 赋予角色
        List<Role> roles = roleService.findRoleListByUserId(userId);
        for (Role role : roles) {
            authorizationInfo.addRole(role.getName());
        }
        // 赋予权限
        List<Resources> resources;
        User user = userService.getById(userId);
        if (ObjectUtil.isNull(user)) {
            return authorizationInfo;
        }
        // 如果用户类型ROOT，则拥有所有权限
        if (StringUtils.equalsIgnoreCase(UserTypeEnum.ROOT.getDesc(), user.getUserType())) {
            resources = resourcesService.findAllResources();
        } else {
            resources = resourcesService.findResourcesByUserId(userId);
        }
        if (CollectionUtil.isNotEmpty(resources)) {
            Set<String> permissionSet = new HashSet<>();
            for (Resources res : resources) {
                String permission;
                if (StringUtils.isNotEmpty(permission = res.getPermission())) {
                    permissionSet.addAll(Arrays.asList(permission.trim().split(StringPool.COMMA)));
                }
            }
            authorizationInfo.setStringPermissions(permissionSet);
        }

        return authorizationInfo;
    }
}
